#include <stdio.h>
#include <stdlib.h>

#include "../common/Helpers.hpp"
#include "../support/Threading.hpp"

#include "SWARMDeps.hpp"
#include "SWARMTaskTag.hpp"
#include "SWARMTaskTagWrapper.hpp"
#include "Externs.hpp"

struct computePreds {
  // condsE must be a tuple holding <zero, false, ... false>
  template<int TASK_ID, int TASKTAGDIM, int INTERTASKDIM, typename... BOOLS, typename... DEPDISTS>
  static void compute(SwarmTaskTag<TASK_ID, TASKTAGDIM, INTERTASKDIM> const & tt,
                      std::tuple<BOOLS...> &condsE, std::tuple<DEPDISTS...> const &dists) {
    // static_assert(tuple_size);
    constexpr int NPARAMS = TASKTAGDIM - INTERTASKDIM;
    
    // Tuple holding <One, t1 ... t_INTERTASKDIM, p1, ... p_NPARAMS>
    static constexpr auto termsE = 
      std::tuple_cat(make_tuple(ConstantTerm()), 
                     makeCompositeTuple<VariableTerm, INTERTASKDIM+NPARAMS>::make());
    //
    /////////////////////////////////////////////////////////////////////////////////    
    // Still TBD how to compress this even more with templates
    //
    static constexpr auto t1 = std::get<1>(termsE);
    static constexpr auto t2 = std::get<2>(termsE);
    static constexpr auto t3 = std::get<3>(termsE);
    static constexpr auto pT = std::get<1+INTERTASKDIM>(termsE);
    // static constexpr auto pM = std::get<2+INTERTASKDIM>(termsE);
    static constexpr auto pN = std::get<3+INTERTASKDIM>(termsE);
    // static constexpr auto pP = std::get<4+INTERTASKDIM>(termsE);
    
    static constexpr auto p1 = 
      FOR(t1, CEIL(-pN-15,16), FLOOR(pT-3,16));
    static constexpr auto p2 = 
      FOR(t2, MAX(t1,-t1-1), 
          MIN(MIN(FLOOR(-8*t1+pT-2,8),FLOOR(8*t1+pN+7,8)),FLOOR(pT+pN-2,16)));
    static constexpr auto p3 = 
      FOR (t3, MAX(MAX(0,CEIL(t1+t2-1,2)),CEIL(16*t2-pN-14,16)), 
           MIN(MIN(FLOOR(pT+pN-2,16),FLOOR(16*t2+pN+14,16)),FLOOR(8*t1+8*t2+pN+15,16)));

    static constexpr auto lbsE = 
      std::make_tuple(ConstantTerm(), 
                      (t1 >= p1.first), (t2 >= p2.first), (t3 >= p3.first));
    static constexpr auto ubsE = 
      std::make_tuple(ConstantTerm(), 
                      (t1 <= p1.second), (t2 <= p2.second), t3 <= (p3.second));

    //
    // END Still TBD how to compress this even more with templates
    /////////////////////////////////////////////////////////////////////////////////  
    //

    // tuple holding <1, tt[0], ... tt[INTERTASKDIM+NPARAMS-1]>
    auto tagAsTupleE = std::tuple_cat(make_tuple(1), tt.toTuple());

    computePredsHelper<TASKTAGDIM, INTERTASKDIM, 1, INTERTASKDIM>::compute
      (condsE, dists, lbsE, ubsE, tagAsTupleE);
  }
};



template<typename THISDECL, typename CODELETDECL, typename SWARM_TAGTABLE, 
         int TASK_ID, int TASKTAGDIM, int INTERTASKDIM>
bool ayncGets(THISDECL THIS, CODELETDECL &CODELET, SWARM_TAGTABLE &stt, 
             const SwarmTaskTag<TASK_ID, TASKTAGDIM, INTERTASKDIM> *this_tag) {

#ifdef MEASURE_OVERHEADS
  Timer oh(GlobalTimers::Singleton().getOverheadGlobal());
#endif

  {
    // Dependence specific
    // static constexpr tag_elem_t d1 = 1, d2 = 1, d3 = 1;
    static constexpr auto dists = 
      std::make_tuple(ConstantTerm(), ConstantTerm(1), ConstantTerm(1), ConstantTerm(1));

    // SWARM-specific
    // Need a const_cast to modify the Tag held by THIS
    SwarmTaskTag<TASK_ID, TASKTAGDIM, INTERTASKDIM> *tt = 
      const_cast<SwarmTaskTag<TASK_ID, TASKTAGDIM, INTERTASKDIM> *>(this_tag);

    SWARMRuntimeWrapper<THISDECL, CODELETDECL, SWARM_TAGTABLE> 
      W(THIS, CODELET, stt);

    auto condsE = makeIdenticalTuple<bool, INTERTASKDIM+1>::make(false);
    computePreds::compute<TASK_ID, TASKTAGDIM, INTERTASKDIM> 
      (*tt, condsE, dists);

    void * dummy;
    SWARMDeps<TASK_ID, TASKTAGDIM, INTERTASKDIM>::genGets(W, *tt, condsE, dists, dummy);

#ifdef DEBUG
    SEQUENTIAL_DUMP1("Suspend = " << W.markedAsyncGet() << " \n");
#endif

    if (W.markedAsyncGet()) return true;
  }

  return false;
}



swarm_CodeletDesc_IMPL_NOCANCEL(heat3d, heat3d, swarm_c_void, swarm_c_void)
const SwarmTagWrapper<SwarmTaskTag<0, 7, 3>> *this_stw = 
             SwarmTagWrapper<SwarmTaskTag<0, 7, 3> >::
             _cast_fromAny((swarm_Any_i *)(void *)THIS);

#ifdef DEBUG
SEQUENTIAL_DUMP1("\n\n");
// RAII style sequentialization
// Threading::SequentializeCurrentScope sct;
SEQUENTIAL_DUMP1("Run heat-3d on thread: " << Threading::my_thread_id() 
                   << " and wrapper @" << this_stw << " with tag " << *(this_stw->tInst));
#endif

// Suspend on asynchronous gets
bool suspend = 
  ayncGets(THIS, swarm_CodeletDesc_CODELET(heat3d), polySwarmTaskTags, this_stw->tInst);
if (suspend) return;

{ // Auto-generated polyhedral loops
  const SwarmTaskTag<0, 7, 3> &this_tag = *(this_stw->tInst);
  int64_t t1 = this_tag[0];
  int64_t t2 = this_tag[1];
  int64_t t3 = this_tag[2];
  int64_t pT = this_tag[0+3];
  // int64_t pM = this_tag[1+3];
  int64_t pN = this_tag[2+3];
  // int64_t pP = this_tag[3+3];

  double (*M)[2][Npadded][Npadded][Npadded] = (double (*)[2][Npadded][Npadded][Npadded])(A);
  int64_t t4, t5, t6, t7, t8, lbv, ubv;
  for (t4=max(max(max(0,ceild(t1+t2-124,125)),ceild(16*t2-pN-998,1000)),ceild(16*t3-pN-998,1000));t4<=min(min(min(min(floord(8*t1+pN+7,500),floord(pT+pN-2,1000)),floord(16*t2+pN+14,1000)),floord(16*t3+pN+14,1000)),floord(8*t1+8*t2+pN+15,1000));t4++) {
    for (t5=max(max(max(max(max(0,8*t1+8*t2),16*t1+1),16*t2-pN),16*t3-pN),1000*t4-pN);t5<=min(min(min(min(min(pT-2,16*t2+14),16*t3+14),1000*t4+998),8*t1+8*t2+15),16*t1+pN+15);t5++) {
      if (t5%2 == 0) {
        for (t6=max(max(16*t2,t5+1),-16*t1+2*t5-15);t6<=min(min(-16*t1+2*t5,16*t2+15),t5+pN);t6++) {
          for (t7=max(16*t3,t5+1);t7<=min(16*t3+15,t5+pN);t7++) {
            lbv=max(1000*t4,t5+1);
            ubv=min(1000*t4+999,t5+pN);
#pragma vector always
#pragma ivdep
            for (t8=lbv;t8<=ubv;t8++) {
              M[0][1][-t5+t6][-t5+t7][-t5+t8]=0.125*(M[0][0][-t5+t6+1][-t5+t7][-t5+t8]-2.0*M[0][0][-t5+t6][-t5+t7][-t5+t8]+M[0][0][-t5+t6-1][-t5+t7][-t5+t8])+0.125*(M[0][0][-t5+t6][-t5+t7+1][-t5+t8]-2.0*M[0][0][-t5+t6][-t5+t7][-t5+t8]+M[0][0][-t5+t6][-t5+t7-1][-t5+t8])+0.125*(M[0][0][-t5+t6][-t5+t7][-t5+t8-1]-2.0*M[0][0][-t5+t6][-t5+t7][-t5+t8]+M[0][0][-t5+t6][-t5+t7][-t5+t8+1])+M[0][0][-t5+t6][-t5+t7][-t5+t8];;
            }
          }
        }
      }else{
        for (t6=max(max(16*t2,t5+1),-16*t1+2*t5-15);t6<=min(min(-16*t1+2*t5,16*t2+15),t5+pN);t6++) {
          for (t7=max(16*t3,t5+1);t7<=min(16*t3+15,t5+pN);t7++) {
            lbv=max(1000*t4,t5+1);
            ubv=min(1000*t4+999,t5+pN);
#pragma ivdep
#pragma vector always
            for (t8=lbv;t8<=ubv;t8++) {
              M[0][0][-t5+t6][-t5+t7][-t5+t8]=0.125*(M[0][1][-t5+t6+1][-t5+t7][-t5+t8]-2.0*M[0][1][-t5+t6][-t5+t7][-t5+t8]+M[0][1][-t5+t6-1][-t5+t7][-t5+t8])+0.125*(M[0][1][-t5+t6][-t5+t7+1][-t5+t8]-2.0*M[0][1][-t5+t6][-t5+t7][-t5+t8]+M[0][1][-t5+t6][-t5+t7-1][-t5+t8])+0.125*(M[0][1][-t5+t6][-t5+t7][-t5+t8-1]-2.0*M[0][1][-t5+t6][-t5+t7][-t5+t8]+M[0][1][-t5+t6][-t5+t7][-t5+t8+1])+M[0][1][-t5+t6][-t5+t7][-t5+t8];;
            }
          }
        }
      }
    }
  }
  
#ifdef DEBUG
  SEQUENTIAL_DUMP1("Start put codelet on wrapper @" << this_stw << " with tag " << this_tag);
#endif
  
  swarm_TagTable_put(&polySwarmTaskTags, 
                     this_stw->_cast_toIdentifiable(), 
                     NULL, swarm_TagTable_COUNT_IMMORTAL, NULL, NULL);
}

#ifdef DEBUG
SEQUENTIAL_DUMP1("Done codelet: " << *(this_stw->tInst) << "\n");
#endif

int current_count = swarm_atomic_incAndGet(count_shared);

#ifdef DEBUG
SEQUENTIAL_DUMP1("Current_count " << current_count << " vs " << global_count << "\n");
#endif

if (current_count >= global_count) {
  finished_heat3d = 1;
  pthread_cond_signal(&final_cv);
}

#ifdef DEBUG
SEQUENTIAL_DUMP1("Inc count: " << current_count << "\n");
#endif

swarm_CodeletDesc_IMPL_END;
